package com.atguigu.gmall.item.service.impl;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.feign.product.ProductSkuDetailsFeignClient;
import com.atguigu.gmall.feign.search.SearchFeignClient;
import com.atguigu.gmall.starter.cache.aspect.annotation.MallCache;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.starter.cache.service.CacheService;
import com.atguigu.gmall.product.entity.SkuImage;
import com.atguigu.gmall.product.entity.SpuSaleAttr;
import com.atguigu.gmall.product.vo.CategoryTreeVo;
import com.atguigu.gmall.product.entity.SkuInfo;
import com.atguigu.gmall.product.vo.SkuDetailVo.CategoryViewDTO;

import com.atguigu.gmall.item.service.SkuDetailsService;
import com.atguigu.gmall.product.vo.SkuDetailVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

/**
 * @author tangsy
 * @Description TODO
 * @date 2022/12/4 11:30
 * @Version 1.0
 */
@Slf4j
@Service
public class SkuDetailsServiceImpl implements SkuDetailsService {

    @Autowired
    ProductSkuDetailsFeignClient skuDetailsFeignClient;

    @Autowired
    ThreadPoolExecutor executor;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    CacheService cacheService;

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    SearchFeignClient searchFeignClient;

    ReentrantLock lock = new ReentrantLock();

    @MallCache(
            cacheKey=RedisConst.SKU_DETAIL_CACHE+"#{#args[0]}",
            bitMapName = RedisConst.SKUID_BITMAP,
            bitMapKey = "#{#args[0]}",
            lockKey = RedisConst.SKU_LOCK+"#{#args[0]}",
            ttl = 7,
            unit = TimeUnit.DAYS
    )
    @Override
    public SkuDetailVo getSkuDetailData(Long skuId)  {
        return getDataFromRpc(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {

        Long increment = redisTemplate.opsForValue().increment("sku:hotscore:" + skuId);
        if (increment % 100 == 0){
            searchFeignClient.updateHotScore(skuId,increment);
        }

    }

//    public SkuDetailVo getSkuDetailDataWithDistLock(Long skuId) throws InterruptedException {
//        //1、先查缓存
//        SkuDetailVo returnVal = cacheService.getFromCache(skuId);
//        //2、缓存有直接返回
//        if (returnVal != null){
//            return returnVal;
//        }
//
//        //3、缓存没有；准备回源。
//        //4、先问bitmap，数据库有没有这个商品；  【防止随机值穿透攻击】
//        boolean contain = cacheService.mightContain(RedisConst.SKUID_BITMAP + skuId, skuId);
//        if (!contain){
//            //bitmap不包含，代表数据库无此数据，直接返回 null
//            return null;
//        }
//        //5、bitmap说有，就去数据库查一查。查之前加锁   【防止缓存击穿】
//        RLock lock = redissonClient.getLock(RedisConst.SKU_LOCK + skuId);
//        boolean tryLock = false;
//        try {
//            //6、加锁； tryLock尝试竞争一下锁
//            tryLock = lock.tryLock();
//            if (tryLock) {
//                //7、抢到锁；回源
//                returnVal = getDataFromRpc(skuId);
//                //8、放到缓存中
//                //cacheService.saveData(skuId,returnVal);
//                return returnVal;
//            } else {
//                //9、睡 300ms 直接查缓存即可
//                TimeUnit.MILLISECONDS.sleep(300);
//                return cacheService.getFromCache(skuId);
//            }
//        } finally {
//            if (tryLock){
//                try {
//                    lock.unlock();
//                }catch (Exception e){}
//            }
//        }
//    }
//
//    public SkuDetailVo getSkuDetailDataWithLocalLock(Long skuId) {
//        //1 先查缓存
//        log.info("商品详情查询开始...");
//        SkuDetailVo returnVal = cacheService.getFromCache(skuId);
//        if (returnVal == null){
//            //2 位图判定
//            boolean contain = cacheService.mightContain(RedisConst.SKUID_BITMAP + skuId, skuId);
//            if (!contain){
//                log.info("bitmap没有，疑似攻击请求，直接打回");
//                return null;
//            }
//            //3 缓存中没有 回源
//            log.info("bitmap有，准备回源。需要加锁防止击穿");
//            //准备拦截击穿风险
//            boolean tryLock = lock.tryLock();
//            if(tryLock){
//                log.info("加锁成功...正在回源");
//                //4、抢锁成功
//                returnVal = getDataFromRpc(skuId);
//                //5、保存到缓存； null值缓存都可能不会被调用
//                //cacheService.saveData(skuId,returnVal);
//                lock.unlock();
//            }else {
//                //6、抢锁失败，500ms 后直接看缓存即可。
//                try {
//                    log.info("加锁失败...稍等直接返回缓存数据");
//                    TimeUnit.MILLISECONDS.sleep(500);
//                    returnVal = cacheService.getFromCache(skuId);
//                } catch (InterruptedException e) {
//                }
//            }
//        }
//        return returnVal;
//    }

    public SkuDetailVo getSkuDetailDataNullData(Long skuId) {

        //1 先查看缓存
        String json = redisTemplate.opsForValue().get("sku:info:" + skuId);
        if (StringUtils.isEmpty(json)){
            //2 缓存没有、回源
            SkuDetailVo result = getDataFromRpc(skuId);
            //3 放入缓存
            redisTemplate.opsForValue().set("sku:info:" + skuId, JSON.toJSONString(result));
            return result;
        }

        //4 缓存中有
        SkuDetailVo result = JSON.parseObject(json, SkuDetailVo.class);

        return result;
    }

    public SkuDetailVo getDataFromRpc(Long skuId) {
        SkuDetailVo skuDetailVo = new SkuDetailVo();

        //1 异步：商品详情
        //2 异步：商品价格
        CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = skuDetailsFeignClient.getSkuInfo(skuId).getData();
            BigDecimal price = skuInfo.getPrice();
            skuDetailVo.setPrice(price);
            return skuInfo;
        },executor);

        CompletableFuture<Void> skuImageCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((result) -> {
            List<SkuImage> skuImageList = skuDetailsFeignClient.getSkuImages(skuId).getData();
            result.setSkuImageList(skuImageList);
            skuDetailVo.setSkuInfo(result);
        },executor);

        //3 异步：分类路径
        CompletableFuture<Void> categoryCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((result) -> {
            CategoryTreeVo treeVo = skuDetailsFeignClient.getCategoryView(result.getCategory3Id()).getData();
            CategoryViewDTO viewDTO = convertToCategoryViewDTO(treeVo);
            skuDetailVo.setCategoryView(viewDTO);
        },executor);

        //4 异步：销售属性
        CompletableFuture<Void> spuSaleAttrCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((result) -> {
            List<SpuSaleAttr> spuSaleAttrList = skuDetailsFeignClient.getSpuSaleAttr(result.getSpuId(), skuId).getData();
            skuDetailVo.setSpuSaleAttrList(spuSaleAttrList);
        },executor);

        //5 异步：valuesSkuJson
        CompletableFuture<Void> jsonCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((result) -> {
            String json = skuDetailsFeignClient.getValuesSkuJson(result.getSpuId()).getData();
            skuDetailVo.setValuesSkuJson(json);
        },executor);

        CompletableFuture
                .allOf(skuImageCompletableFuture,categoryCompletableFuture,spuSaleAttrCompletableFuture,jsonCompletableFuture)
                .join();
        return skuDetailVo;
    }

    private CategoryViewDTO convertToCategoryViewDTO(CategoryTreeVo treeVo) {
        CategoryViewDTO viewDTO = new CategoryViewDTO();
        viewDTO.setCategory1Id(treeVo.getCategoryId());
        viewDTO.setCategory1Name(treeVo.getCategoryName());

        CategoryTreeVo child1 = treeVo.getCategoryChild().get(0);
        viewDTO.setCategory2Id(child1.getCategoryId());
        viewDTO.setCategory2Name(child1.getCategoryName());

        CategoryTreeVo child2 = child1.getCategoryChild().get(0);
        viewDTO.setCategory3Id(child2.getCategoryId());
        viewDTO.setCategory3Name(child2.getCategoryName());
        return viewDTO;
    }

}
